package models;

import play.libs.F.*;
import java.awt.*;
import java.util.ArrayList;
import java.util.List;

/**
 * @author Andriy
 **/
public class StatusManager {

        public static final int INIT_SIZE = 50;
        /**
         * Save id of users which listen 'public' events (such us provider declared, user requested e.t.c)
         * */
        public List<Long> users = new ArrayList<Long>(INIT_SIZE);

        /**
         * Save id of user which sign in service
         * */
        public void signIn(long userId) {
                this.users.add(userId);
        }

        /**
         * Create event 'ProvDeclared' and publish it in event steams of all free users. Set status in Trips and Provs models
         * */
        public void provDeclared(long provId, float pLat, float pLong) {
                Provs.getEventStream(provId);
                for (Long id : users) {
                        Users.getEventStream(id).publish(new ProvDeclared(provId, pLat, pLong));
                }
                Provs.setStatus(provId, Status.p_declared);
                //work with trip
                Trips trip = Trips.find("select trip from Trips trip where trip.provId=? and trip.status<>?", provId, Status.ended).first();
                if (trip == null){
                        trip = new Trips(provId, pLat, pLong);
                }
                else
                {
                        trip.setParam(Status.p_declared, pLat, pLong);
                }
        }

        /**
         *  Create event 'userChooseProv' and publish it in event steams of all free users.
         * */
        public void userChooseTaxi(long userId, long provId, double u_lat, double u_long) {
                Provs.getEventStream(provId).publish(new UserChooseProv(provId, userId, u_lat, u_long));
                for (Long id : users) {
                        Users.getEventStream(id).publish(new UserChooseProv(provId, userId, u_lat, u_long));
                }
                Trips.setParam(Status.u_requested, provId, userId);
        }

        /**
         *  Create event 'ProvAccept' and publish it in event steams of user.
         * */
        public void provAccept(String serviceInfo, String time, long userId, long provId) {
                Users.getEventStream(userId).publish(new ProvAccept(serviceInfo, time, userId, provId));
                Provs.setStatus(provId, Status.p_accepted);
                //work with trip
                Trips.setParam(Status.p_accepted, provId, userId);
        }

        /**
         *  Create event 'ProvAcceptCanceled' and publish it in event steams of user.
         * */
        public void provAcceptCanceled(long userId, long provId) {
                Users.getEventStream(userId).publish(new ProvAcceptCanceled(userId, provId));
                Provs.getEventStream(provId).publish(new ProvAcceptCanceled(userId, provId));
                Provs.setStatus(provId, Status.p_acceptedCanceled);
                //work with trip
                //Trips.setParam(Status.p_declared, provId);
        }

        /**
         *  Create event 'UserConfirmed' and publish it in event steams of all free user.
         * */
        public void userConfirmed(long userId, long provId, float lat, float lng) {
                for (Long id : users) {
                        Users.getEventStream(id).publish(new UserConfirmed(userId,provId));
                }
                delPublicListener(userId);
                Provs.getEventStream(provId).publish(new UserConfirmed(userId,provId));
                Trips.setParam(Status.u_confirmed, provId, userId, lat, lng);
                Provs.setStatus(provId, Status.u_confirmed);
        }

        /**
         *  Create event 'UserCanceled' and publish it in event steams of provider.
         * */
        public void userCanceled(long userId, long provId) {
                Provs.getEventStream(provId).publish(new UserCanceled(userId, provId));
                Users.getEventStream(userId).publish(new UserCanceled(userId, provId));
                Provs.setStatus(provId, Status.u_canceled);
                Trips.setParam(Status.p_declared, provId);
        }

        /**
         *  Create event 'LockedUser' and publish it in event steams of user.
         * */
        public void lockedUser(long provId, long userId) {
                Users.getEventStream(userId).publish(new LockedUser(userId));
                Trips.setParam(Status.p_locked, provId);
                Provs.setStatus(provId, Status.p_locked);
        }

        /**
         *  Create event 'ProvReached' and publish it in event steams of user.
         **/
        public void provReached(long provId, long userId) {
                Users.getEventStream(userId).publish(new ProvReached(userId));
                Trips.setParam(Status.p_reached, provId);
                Provs.setStatus(provId, Status.p_reached);
        }

        /**
         * @param userId - id of user which do not need listen 'public' events
         **/
        public void delPublicListener(long userId) {
                this.users.remove(userId);
        }

        /**
         * @param lastReceived - number of last received event
         * @param provId - id of provider which get events
         *	For long polling, as we are sometimes disconnected, we need to pass
         * 	the last event seen id (lastReceived), to be sure to not miss any event
         * */
        public Promise<List<IndexedEvent<StatusManager.Event>>> nextProvEvent(long lastReceived, long provId) {
                return Provs.getEventStream(provId).nextEvents(lastReceived);
        }

        /**
         * @param lastReceived - number of last received event
         * @param userId - id of user which get events
         * */
        public Promise<List<IndexedEvent<StatusManager.Event>>> nextUserEvent(long lastReceived, long userId) {
                return Users.getEventStream(userId).nextEvents(lastReceived);
        }

        public static abstract class Event {

                final public Status status;
                final public Long timeChange;

                public Event(Status status) {
                        this.status = status;
                        this.timeChange = System.currentTimeMillis();
                }
        }

        public static class ProvDeclared extends Event {

                final long provId;
                final Point.Float coordinates = new Point.Float();

                public ProvDeclared(long provId, float pLat, float pLong) {
                        super(Status.p_declared);
                        this.provId = provId;
                        this.coordinates.setLocation(pLat, pLong);
                }
        }

        public static class UserChooseProv extends Event {

                final public long userId;
                final public long provId;
				final public double u_lat;
				final public double u_long;

                public UserChooseProv(long provId, long userId, double u_lat, double u_long) {
                        super(Status.u_requested);
                        this.userId = userId;
                        this.provId = provId;
						this.u_lat = u_lat;
						this.u_long = u_long;
                }
        }

        public static class ProvAccept extends Event {

                final public String serviceInf;
                final public String time;
                final public long userId;
                final public long provId;

                public ProvAccept(String serviceInf, String time, long userId, long provId) {
                        super(Status.p_accepted);
                        this.serviceInf = serviceInf;
                        this.time = time;
                        this.userId = userId;
                        this.provId = provId;
                }
        }

        public static class ProvAcceptCanceled extends Event {

                final public long userId;
                final public long provId;

                public ProvAcceptCanceled(long userId, long provId) {
                        super(Status.p_acceptedCanceled);
                        this.userId = userId;
                        this.provId = provId;
                }
        }

        public static class UserConfirmed extends Event {
			    final public long provId;
                final public long userId;

                public UserConfirmed(long userId, long provId) {
                        super(Status.u_confirmed);
                        this.userId = userId;
						this.provId = provId;
                }
        }

        public static class LockedUser extends Event {

                final public long userId;

                public LockedUser(long userId) {
                        super(Status.p_locked);
                        this.userId = userId;
                }
        }

        public static class ProvReached extends Event {

                final public long userId;

                public ProvReached(long userId) {
                        super(Status.p_reached);
                        this.userId = userId;
                }
        }

        public static class UserCanceled extends Event {

                final public long userId;
                final public long provId;

                public UserCanceled(long userId, long provId) {
                        super(Status.u_canceled);
                        this.userId = userId;
                        this.provId = provId;
                }
        }
        //StatusManager factory
        private static StatusManager instance = null;

        public static StatusManager get() {
                if (StatusManager.instance == null) {
                        StatusManager.instance = new StatusManager();
                }
                return StatusManager.instance;
        }
}
